﻿//++
//
// Copyright (c) Microsoft Corporation.  All rights reserved.
//
// Module Name:
//
//  DispatcherClassFactory.cs
//    
// Abstract:
//
//  This module is a factory class for abstracting the creation
//  of complex objects away from the dispatcher service class.
//  
//--

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Security.AccessControl;
using System.Security.Permissions;
using System.Security.Principal;
using System.Workflow.Runtime;
using Microsoft.EnhancedReminders.Dispatcher.Workflow;
using Microsoft.OfficeCommunicationsServer.Applications.Common;
using Microsoft.Rtc.Collaboration;
using Microsoft.Rtc.Workflow.Activities;

namespace Microsoft.EnhancedReminders.Dispatcher
{
    /// <summary>
    /// Factory class for building the individual components used by
    /// the dispatcher service.
    /// </summary>
    public class DispatcherClassFactory : IDisposable
    {
        /// <summary>
        /// Workflow runtime to be used across all workflow-dependent components.
        /// </summary>
        private static WorkflowRuntime _workflowRuntime;

        private IOcsEndpointProxy _ocsProxy;

        /// <summary>
        /// Event Handler for notifying listeners when the registration state of the
        /// SIP endpoint has changed.
        /// </summary>
        public event EventHandler<LocalEndpointStateChangedEventArgs> RegistrationStateChanged;

        /// <summary>
        /// Synchronously creates and registers the SipEndpoint that will be used across the
        /// OCS components used by the dispatcher, initializes Windows Workflow for voice
        /// notifications, and creates the directory to be used for temporarily storing audio
        /// files for voicemail reminders.
        /// </summary>
        public virtual void Initialize()
        {
            Debug.Assert(_ocsProxy == null, "_ocsProxy == null");

            if (Helpers.GetSetting<bool>("RunInClientMode", false))
            {
                _ocsProxy = new OcsEndpointProxy();
            }
            else
            {
                _ocsProxy = new ApplicationEndpointProxy();
            }

            _ocsProxy.RegistrationStateChanged += OcsProxyRegistrationStateChanged;
            _ocsProxy.Initialize();

            InitializeWorkflow();
            CreateVoicemailDirectory();
        }

        private void OcsProxyRegistrationStateChanged(object sender, LocalEndpointStateChangedEventArgs e)
        {
            EventHandler<LocalEndpointStateChangedEventArgs> callback = this.RegistrationStateChanged;

            if (callback != null)
            {
                callback(sender, e);
            }
        }

        private static void InitializeWorkflow()
        {
            Logger.WriteTraceInformation("Initializing workflow services.");

            _workflowRuntime = new WorkflowRuntime();
            _workflowRuntime.AddService(new CommunicationsWorkflowRuntimeService());
            _workflowRuntime.AddService(new TrackingDataWorkflowRuntimeService());
            _workflowRuntime.AddService(new JoinNowInstantMessageService());
            _workflowRuntime.AddService(new JoinNowVoiceService());
            _workflowRuntime.StartRuntime();
        }

        private static void CreateVoicemailDirectory()
        {
            Logger.WriteTraceInformation("Creating scratch directory.");

            try
            {
                // if the directory already exists, we want to delete it to remove
                // any old scratch files and to reset the ACLs.
                if (Directory.Exists(DispatcherHelpers.ScratchDirectory))
                {
                    Directory.Delete(DispatcherHelpers.ScratchDirectory, true);
                }

                DirectorySecurity accessControl = new DirectorySecurity();
                accessControl.SetOwner(new NTAccount(Environment.UserDomainName, Environment.UserName));
                accessControl.AddAccessRule(new FileSystemAccessRule(new NTAccount(Environment.UserDomainName, Environment.UserName), FileSystemRights.FullControl, AccessControlType.Allow));
                Directory.CreateDirectory(DispatcherHelpers.ScratchDirectory, accessControl);
            }
            catch (UnauthorizedAccessException e)
            {
                OperationalLogs.LogScratchDirectoryCreateError(DispatcherHelpers.ScratchDirectory, e);
                throw;
            }
            catch (PathTooLongException e)
            {
                OperationalLogs.LogScratchDirectoryCreateError(DispatcherHelpers.ScratchDirectory, e);
                throw;
            }
            catch (DirectoryNotFoundException e)
            {
                OperationalLogs.LogScratchDirectoryCreateError(DispatcherHelpers.ScratchDirectory, e);
                throw;
            }
            catch (IOException e)
            {
                OperationalLogs.LogScratchDirectoryCreateError(DispatcherHelpers.ScratchDirectory, e);
                throw;
            }
        }

        /// <summary>
        /// Asynchronously tears down OCS components.
        /// </summary>
        public virtual void Uninitialize()
        {
            Debug.Assert(_workflowRuntime != null, "workflowRuntime != null");
            Debug.Assert(_ocsProxy != null, "_ocsProxy != null");

            _workflowRuntime.StopRuntime();
            RemoveVoicemailDirectory();
            _ocsProxy.Uninitialize();
        }

        private static void RemoveVoicemailDirectory()
        {
            try
            {
                Directory.Delete(DispatcherHelpers.ScratchDirectory, true);
            }
            catch (DirectoryNotFoundException e)
            {
                OperationalLogs.LogScratchDirectoryCleanupWarning(DispatcherHelpers.ScratchDirectory, e);
            }
            catch (PathTooLongException e)
            {
                OperationalLogs.LogScratchDirectoryCleanupWarning(DispatcherHelpers.ScratchDirectory, e); 
                throw;
            }
            catch (UnauthorizedAccessException e)
            {
                OperationalLogs.LogScratchDirectoryCleanupWarning(DispatcherHelpers.ScratchDirectory, e);
                throw;
            }
            catch (IOException e)
            {
                OperationalLogs.LogScratchDirectoryCleanupWarning(DispatcherHelpers.ScratchDirectory, e);
                throw;
            }
        }

        /// <summary>
        /// Obtains an appropriate subclass of IMessageSender based on a notification
        /// item.
        /// </summary>
        /// <returns>An instance of IMessageSender based on the given NotificationItem or null if a IMessageSender could not be created</returns>
        public virtual IMessageSender CreateMessageSender(NotificationItem notification, PresenceObtainedEventArgs contactCard)
        {
            Debug.Assert(notification != null, "notification != null");
            Debug.Assert(contactCard != null, "contactCard != null");

            IList<IMessageSender> senders = null;

            ReminderItem reminder = GetReminder(notification, contactCard);

            if (reminder != null)
            {
                if (notification.IsJoinNow)
                {
                    senders = CreateSendersFromChannelList(notification, contactCard, reminder.JoinNowChannelList.Channels);
                }
                else
                {
                    senders = CreateSendersFromChannelList(notification, contactCard, reminder.NotificationChannelList.Channels);
                }
            }

            if (senders == null || senders.Count == 0)
            {
                return null;
            }
            else
            {
                return new CompositeMessageSender(senders);
            }
        }

        private static ReminderItem GetReminder(NotificationItem notification, PresenceObtainedEventArgs contactCard)
        {
            VoicemailNotificationItem voicemailNotification = notification as VoicemailNotificationItem;

            if (voicemailNotification != null)
            {
                return voicemailNotification.GetReminderItem(contactCard.Presence);
            }
            else
            {
                return notification.AssertCast<CalendarNotificationItem>().ReminderItem;
            }
        }

        private IList<IMessageSender> CreateSendersFromChannelList(NotificationItem notification, PresenceObtainedEventArgs contactCard, List<NotificationChannel> channels)
        {
            IList<IMessageSender> senders = new List<IMessageSender>();

            foreach (NotificationChannel channel in channels)
            {
                if (channel.IsEnabled)
                {
                    NotificationCustomChannel customChannel = channel as NotificationCustomChannel;

                    // Note: This list will expand as more channels are supported.
                    if (channel.NotificationChannelType == NotificationChannelType.Communicator)
                    {
                        string sipAddress = null;

                        if (channel.Id == NotificationChannel.CommunicatorId)
                        {
                            sipAddress = contactCard.SipAddress;
                        }
                        else if (customChannel != null)
                        {
                            sipAddress = channel.Value;
                        }
                        else
                        {
                            Debug.Fail("Unknown notification ID for Communicator channel.");
                        }

                        if (sipAddress != null)
                        {
                            try
                            {
                                senders.Add(new InstantMessageSender(CreateConversation(sipAddress), _workflowRuntime, notification));
                            }
                            catch (ArgumentException e)
                            {
                                Logger.WriteTraceError("Error parsing destination address: {0} {1} {2}", sipAddress, e.Message, e.StackTrace);
                            }
                        }
                        else
                        {
                            Logger.WriteTraceWarning(notification.Context, "Attempted to deliver notification to {0} via custom IM channel but could not access contact card info.", notification.EmailAddress);
                        }
                    }
                    else if (channel.NotificationChannelType == NotificationChannelType.Phone)
                    {
                        string contactNumber = null;

                        if (customChannel != null)
                        {
                            contactNumber = channel.Value;
                        }
                        else if (channel.Id == NotificationChannel.WorkPhoneId)
                        {
                            contactNumber = contactCard.DeskPhone;
                        }
                        else if (channel.Id == NotificationChannel.MobilePhoneId)
                        {
                            contactNumber = contactCard.MobilePhone;
                        }
                        else if (channel.Id == NotificationChannel.HomePhoneId)
                        {
                            contactNumber = contactCard.HomePhone;
                        }
                        else if (channel.Id == NotificationChannel.OtherPhoneId)
                        {
                            contactNumber = contactCard.OtherPhone;
                        }

                        if (contactNumber != null)
                        {
                            try
                            {
                                senders.Add(new VoiceMessageSender(CreateConversation(contactNumber), _workflowRuntime, notification));
                            }
                            catch (ArgumentException e)
                            {
                                Logger.WriteTraceError("Error parsing destination address: {0} {1} {2}", contactNumber, e.Message, e.StackTrace);
                            }
                        }
                        else
                        {
                            Logger.WriteTraceWarning(notification.Context, "Attempted to deliver notification to {0} with phone type {1}, but could not access contact card info.", notification.EmailAddress, channel);
                        }
                    }
                    else if (channel.NotificationChannelType == NotificationChannelType.SMS)
                    {
                        senders.Add(new SmsMessageSender(notification, channel.Value));
                    }
                    else
                    {
                        Debug.Assert(false, "Unsupported notification channel type hit. Only Communicator and phone channels are supported.");
                    }
                }
            }

            return senders;
        }

        /// <summary>
        /// Creates a new Conversation for implementations of IMessageSender to use
        /// </summary>
        public virtual Conversation CreateConversation(string targetAddress)
        {
            Debug.Assert(_ocsProxy != null, "_ocsProxy != null");
                       
            return _ocsProxy.CreateConversation(targetAddress);
        }

        /// <summary>
        /// Obtains an appropriate subclass of IPresenceProvider for doing presence
        /// lookups.
        /// </summary>
        /// <returns>An instance of IPresenceProvider.</returns>
        public virtual IPresenceProvider CreatePresenceProvider()
        {
            Debug.Assert(_ocsProxy != null, "_ocsProxy != null");

            return _ocsProxy.CreatePresenceProvider();
        }

        /// <summary>
        /// Performs an Active Directory query to obtain the SIP address of an ER user. The SIP address is needed
        /// for performing presence queries.
        /// </summary>
        /// <param name="notification">A NotificationItem containing the email address that would be listed under the mail property in AD</param>
        /// <returns>The SIP address for the AD user with the given email address or null if a sip address could not be found.</returns>
        [EnvironmentPermissionAttribute(SecurityAction.LinkDemand, Unrestricted = true)]
        public virtual string ObtainSipAddress(NotificationItem notification)
        {
            Debug.Assert(notification != null, "notification != null");
            Debug.Assert(notification.EmailAddress != null, "notification.EmailAddress != null");

            return EnhancedRemindersHelpers.ObtainSipAddress(notification.Context, notification.EmailAddress);
        }

        #region IDisposable Members

        /// <summary>
        /// Disposes of the timer used for reconnecting to OCS
        /// </summary>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// Implements disposing of the timer used for reconnecting to OCS
        /// </summary>
        private void Dispose(bool disposing)
        {
            if (disposing)
            {
                OcsEndpointProxy proxy = _ocsProxy as OcsEndpointProxy;

                if (proxy != null)
                {
                    proxy.Dispose();
                    _ocsProxy = null;
                }
            }

        }
        #endregion
    }
}